///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Microsoft Corporation.  All rights reserved.
// Portions Copyright (c) GHI Electronics, LLC.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace GHIElectronics.NETMF.Net
{
    using GHIElectronics.NETMF.Net.Sockets;
    using System.Collections;
    using System.Threading;
    using System;
    //using Microsoft.SPOT;

    /// <summary>
    /// Provides a simple, programmatically controlled HTTP protocol listener.
    /// This class cannot be inherited.
    /// </summary>
    /// <remarks>
    /// This class enables using a socket to receive data that uses the HTTP
    /// protocol.
    /// </remarks>
    public class HttpListener
    {
        /// <summary>
        /// This value is the number of connections that can be ready but are
        /// not retrieved through the Accept call.
        /// </summary>
        /// <remarks>
        /// This value is passed to the <b>Listen</b> method of the socket.
        /// </remarks>
        private const int MaxCountOfPendingConnections = 1;

        /// <summary>
        /// The time we keep connection idle with HTTP 1.1
        /// This is one minute.
        /// </summary>
       internal const int DefaultKeepAliveMilliseconds = 60000;

        /// <summary>
        /// Server socket for incoming connections.
        /// </summary>
        private Socket m_listener;

        /// <summary>
        /// The MAXIMUM length, in kilobytes (1024 bytes), of the request
        /// headers.
        /// </summary>
        internal int m_maxResponseHeadersLen;

        /// <summary>
        /// Port number for the server socket.
        /// </summary>
        private int m_Port;

        /// <summary>
        /// Indicates whether the listener is started and is currently accepting
        /// connections.
        /// </summary>
        private bool m_ServiceRunning;

        /// <summary>
        /// Indicates whether the listener has been closed
        /// </summary>
        private bool m_Closed;

        /// <summary>
        /// Array of connected client sockets.
        /// </summary>
        private ArrayList m_ClientStreams;

        /// <summary>
        /// Creates an HTTP or HTTPS listener on the standard ports.
        /// </summary>
        /// <param name="prefix">Prefix ( http or https ) to start listen</param>
        /// <remarks>In the desktop version of .NET, the constructor for this
        /// class has no arguments.</remarks>
        public HttpListener(string prefix)
        {
            InitListener(prefix, -1);
        }

        /// <summary>
        /// Creates an HTTP or HTTPS listener on the specified port.
        /// </summary>
        /// <param name="prefix">The prefix for the service, either "http" or
        /// "https".</param>
        /// <param name="port">The port to start listening on.  If -1, the
        /// default port is used (port 80 for http, or port 443 for https).
        /// </param>
        /// <remarks>In the desktop version of .NET, the constructor for this
        /// class has no arguments.</remarks>
        public HttpListener(string prefix, int port)
        {
            InitListener(prefix, port);
        }

        /// <summary>
        /// Initializes the listener.
        /// </summary>
        /// <param name="prefix">The prefix for the service, either "http" or
        /// "https".</param>
        /// <param name="port">The port to start listening on.  If -1, the
        /// default port is used (port 80 for http, or port 443 for https).
        /// </param>
        private void InitListener(string prefix, int port)
        {
            switch (prefix.ToLower())
            {
                case "http":
                    {
                        m_Port = Uri.HttpDefaultPort;
                        break;
                    }
                default: 
                    throw new ArgumentException("Prefix should be http");
            }

            if (port != -1)
            {
                m_Port = port;
            }

            // Default members initialization
            m_maxResponseHeadersLen = 4;
        }


        /// <summary>
        /// Shuts down the <itemref>HttpListener</itemref> object immediately,
        /// discarding all currently queued requests.
        /// </summary>
        /// <remarks>This method disposes of all resources held by this
        /// listener.  Any pending requests are unable to complete.  To shut
        /// down the <itemref>HttpListener</itemref> object after processing
        /// currently queued requests, use the
        /// <see cref='System.Net.HttpListener.Close'/> method.
        /// <para>
        /// After calling this method, you will receive an
        /// <see cref='ObjectDisposedException'/> if you attempt to use this
        /// <itemref>HttpListener</itemref>.
        /// </para>
        /// </remarks>
        public void Abort()
        {
            lock (this)
            {
                // First we shut down the service.
                Close();
                
                // Now we need to go through list of all client sockets and close all of them.
                // This will cause exceptions on read/write operations on these sockets.
                foreach (OutputNetworkStreamWrapper netStream in m_ClientStreams)
                {
                    netStream.Close();
                }
                m_ClientStreams.Clear();
            }
        }

        /// <summary>
        /// Allows this instance to receive incoming requests.
        /// </summary>
        /// <remarks>This method must be called before you call the
        /// <see cref="System.Net.HttpListener.GetContext"/> method.   If
        /// the service was already started, the call has no effect.  After you
        /// have started an <itemref>HttpListener</itemref> object, you can use
        /// the <see cref='System.Net.HttpListener.Stop'/> method to stop it.
        /// </remarks>
        public void Start()
        {
            lock (this)
            {
                if (m_Closed) 
                    throw new ObjectDisposedException();
                
                // If service was already started, the call has no effect.
                if (m_ServiceRunning)
                    return;
                
                m_listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, m_Port);
                m_listener.Bind(endPoint);
                m_listener.Listen(1);

                m_ServiceRunning = true;
            }
        }

        /// <summary>
        /// Shuts down the <itemref>HttpListener</itemref> after processing all
        /// currently queued requests.
        /// </summary>
        /// <remarks>After calling this method, you can no longer use the
        /// <itemref>HttpListener</itemref> object.  To temporarily pause an
        /// <itemref>HttpListener</itemref> object, use the
        /// <see cref='System.Net.HttpListener.Stop'/> method.</remarks>
        public void Close()
        {
            lock(this)
            {
                // close does not throw
                try
                {
                    Stop();
                }
                catch
                {
                }
                
                m_Closed = true;
            }
        }

        /// <summary>
        /// Causes this instance to stop receiving incoming requests.
        /// </summary>
        /// <remarks>If this instance is already stopped, calling this method
        /// has no effect.
        /// <para>
        /// After you have stopped an <itemref>HttpListener</itemref> object,
        /// you can use the <see cref='System.Net.HttpListener.Start'/> method
        /// to restart it.
        /// </para>
        /// </remarks>
        public void Stop()
        {   
            // Need to lock access to object, because Stop can be called from a
            // different thread.
            lock (this)
            {
                if (m_Closed) 
                    throw new ObjectDisposedException();
            
                // If the service is already stopped, return.  This case happens
                // routinely when one thread calls Stop().  The accept thread
                // throws an exception, which causes another call to Stop while
                // the service is already stopped.
                if (!m_ServiceRunning)
                {
                    return;
                }

                m_ServiceRunning = false;
                
                // We close the server socket that listen for incoming connection.
                // Connections that already accepted are processed.
                // Connections that has been in queue for server socket, but not accepted, are lost.
                m_listener.Close();
            }
        }

        /// <summary>
        /// Waits for an incoming request and returns when one is received.
        /// </summary>
        /// <returns>
        /// An <see cref="System.Net.HttpListenerContext"/> object that
        /// represents a client request.
        /// </returns>
        /// <exception cref="SocketException">A socket call failed. Check the
        /// exception's ErrorCode property to determine the cause of the exception.</exception>
        /// <exception cref="InvalidOperationException">This object has not been started or is
        /// currently stopped or The HttpListener does not have any Uniform Resource Identifier
        /// (URI) prefixes to respond to.</exception>
        /// <exception cref="ObjectDisposedException">This object is closed.</exception>
        /// <example>This example shows how to call the
        /// <itemref>GetContext</itemref> method.
        /// <code>
        /// HttpListener myListener = new HttpListener("http", -1);
        /// myListener.Start();
        /// while (true)
        /// {
        ///     HttpListenerResponse response = null;
        ///     try
        ///     {
        ///         Debug.Print("Waiting for requests");
        ///         HttpListenerContext context = myListener.GetContext();
        /// </code>
        /// </example>
        public HttpListenerContext GetContext()
        {
            // Protects access for simulteneous call for GetContext and Close or Stop.
            lock (this)
            {
                if (m_Closed) 
                    throw new ObjectDisposedException();
            
                if (!m_ServiceRunning) 
                    throw new InvalidOperationException();
            }

            Socket clientSock = null;
            try
            {
                clientSock = m_listener.Accept();
            }
            catch (SocketException )
            {
                //Debug.Print("Socket exception " + socketException.Message);
            }

            if (clientSock != null)
            {
                bool incomingData = clientSock.Poll(5000000, SelectMode.SelectRead);

                if (incomingData && clientSock.Available > 0)
                {
                    NetworkStream netStream = new NetworkStream(clientSock, true);
                    OutputNetworkStreamWrapper outputStreamWrap = new OutputNetworkStreamWrapper(clientSock, netStream);
                    return new HttpListenerContext(outputStreamWrap, this);
                }
                else
                {
                    clientSock.Close();
                }
            }

            return null;
        }

        /// <summary>
        /// Gets whether the <itemref>HttpListener</itemref> service was started
        /// and is waiting for client connections.
        /// </summary>
        /// <value><itemref>true</itemref> if the
        /// <itemref>HttpListener</itemref> was started; otherwise,
        /// <itemref>false</itemref>.</value>
        public bool IsListening
        {
             get { return m_ServiceRunning; }
        }

        /// <summary>
        /// Gets or sets the maximum allowed length of the response headers, in
        /// KB.
        /// </summary>
        /// <value>The length, in kilobytes (1024 bytes), of the response
        /// headers.</value>
        /// <remarks>
        /// The length of the response header includes the response status line
        /// and any extra control characters that are received as part of the
        /// HTTP protocol.  A value of -1 means no limit is imposed on the
        /// response headers; a value of 0 means that all requests fail.  If
        /// this property is not explicitly set, it defaults to 4 (KB).
        /// </remarks>
        public int MaximumResponseHeadersLength
        {
            get { return m_maxResponseHeadersLen; }
            set
            {
                if (value <= 0 && value != -1)
                {
                    throw new ArgumentOutOfRangeException();
                }

                m_maxResponseHeadersLen = value;
            }
        }
    }
}


